package util;

//import de.zkm.circlemenus.Util;
import geom.*;
import processing.core.*;


/**Envelope to make non-linear interpolation between to values over a specified time segment.
 * Modified based on a TCB(Tension Continuinity Bias) spline, leaving the Continuity value out.
 * Uses the TCB spline to generate a segmented linear envelope.
 * @author henrik
 *
 */
public class Envelope implements PConstants{
	/**Start time the envelope.*/
	private float t1;
	/**End time of the envelope.*/
	private float t2;
	/**End time of the envelope.*/
	private float v1;
	/**Value at the end of the envelope.*/
	private float v2;
	/**Outgoing tangent of the start point, calculated from the tension and bias values.*/
	private float tan1;
	/**Incoming tangent of the end point, calculated from the tension and bias values.*/
	private float tan2;
	/**The number of steps used to create the segmentend linear envelope.*/
	private int res;
	/**Array holding the time/value pairs for the linear envelope.*/
	float[][] points;
	
	/**Constructor, creates and initializes an envelope.
	 * @param t1			Start time the envelope.
	 * @param v1			Value of the beginning of the envelope.
	 * @param t2			End time of the envelope.
	 * @param v2			Value at the end of the envelope.
	 * @param tension	Tension, defines how much the envelope (de)accelerates at the start and end points.
	 * @param bias		Bias, defines if the envelope accellerates smoothly at the beginning and stops abruptly 
	 * 					or the otherway around. A bias of 0.3 creates a equal bias.
	 * @param res		The number of steps used to create the segmentend linear envelope.
	 */
	public Envelope(float t1, float v1, float t2, float v2, float tension, float bias, int res) {
		this.t1 = t1;
		this.v1 = v1;
		this.t2 = t2;
		this.v2 = v2;
		this.res = res;
		tan1 = (v2 - v1) * tension * (1 - bias) * 2f;
		tan2 = (v2 - v1) * tension * bias * 2f;
		calcPoints();
	}
	
	/**Scales the linear envelope to the specified time segment.
	 * @param t1	Start time.
	 * @param t2	End time.
	 */
	public void scaleTime(float t1, float t2){
		if(this.t1 != this.t2){
			float mult = (t2 - t1) / (this.t2 - this.t1);
			for(int i = 0; i < points.length; i++){
				points[i][0] -= this.t1;
				points[i][0] *= mult;
				points[i][0] += t1;
				
			}
			this.t1 = t1;
			this.t2 = t2;
		}
		else{
			this.t1 = t1;
			this.t2 = t2;
			calcPoints();
		}
		
	}
	
	/**Scales the values of the linear envelope to the specified values.
	 * @param v1	Start value.
	 * @param v2	End value.
	 */
	public void scaleValues(float v1, float v2){
		float mult = (v2 - v1) / (this.v2 - this.v1);
		for(int i = 0; i < points.length; i++){
			points[i][1] -= this.v1;
			points[i][1] *= mult;
			points[i][1] += v1;
			
		}
		this.v1 = v1;
		this.v2 = v2;
	}
	
	public float interpolate(float v1, float v2, float t){
		t = getPoint(t);
		return Geom.interpolate(v1, v2, t);
	}
	
	public float[] interpolate(float[] v1, float[] v2, float t){
		t = getPoint(t);
		float[] v = new float[Math.min(v1.length, v2.length)];
		for(int i = 0; i < v.length; i++){
			v[i] = Geom.interpolate(v1[i], v2[i], t);
		}
		return v;
	}
	
	/**Returns a value from the TCB spline at a specified time. Used for calculating the linear envelope.
	 * @param t	Time to find the value for
	 * @return	The value at the specified time.
	 */
	private float calcPoint(float t){
		//PApplet.println("t:" + t);
		if(t1 == t2) return v1;
		t = (t - t1) /(t2 - t1);
		//PApplet.println("t:" + t);
		float[] h = hermiteFunctions(t);
		return h[0] * v1 + h[1] * v2 + h[2] * tan1 + h[3] * tan2;
		
	}
	
	/**Returns the values of the 4 hermite functions.
	 * @param s	value between 0 and 1.
	 * @return	Array containing the hermite function values.
	 */
	private float[] hermiteFunctions(float s){
		float[] h = new float[4];
		//h[0] = 1-s;
		//h[1] = s;
		h[0] = 2 *s*s*s - 3 * s*s + 1;
		h[1] = -2 * s*s*s + 3 *s*s;
		h[2] = s*s*s - 2 * s*s + s;
		h[3] = s*s*s - s*s;
		return h;
	}
	
	
	/**Calculates the linear envelope.*/
	private void calcPoints(){
		float tConst = (t2 - t1) / res;
		points = new float[res+1][2];
		for(int i = 0; i <= res; i++){
			points[i][0] = t1 + i * tConst;
			points[i][1] = calcPoint(t1 + i * tConst);
		}
	}
	
	int prevI = 1;
	/**Returns a value at a specified time, using the linear envelope.
	 * @param t	Time to calculate value for.
	 * @return	Value at specified time.
	 */
	public float getPoint(float t){
		float[] p1 = null;
		float[] p2 = null;
		int i = 0;
		
		if(t < t1) return v1;
		if(t > t2) return v2;
		if(t1 == t2 && t == t2) return v2;
		while(i < points.length-1){
			if(t >= points[prevI-1][0] && t <= points[prevI][0]){
				p1 = points[prevI-1];
				p2 = points[prevI];
				t = (t - p1[0]) / (p2[0] - p1[0]);
				return (1 -  t) * p1[1] + t * p2[1];
			}
			i++;
			prevI++;
			if(prevI >= points.length) prevI = 1;
		}
		return Float.NaN;
	}
}
